// file: arch/hardware/apic.c
// autor: jiangxinpeng
// time: 2023.1.23
// copyright: (C) 2020-2050 by jiangxinpeng,all right are reserved.
#include <arch/apic.h>
#include <arch/cpu.h>
#include <arch/pit.h>
#include <arch/x86.h>
#include <arch/page.h>
#include <arch/mp.h>
#include <arch/pymem.h>
#include <os/timer.h>
#include <os/debug.h>
#include <arch/memio.h>
#include <os/softirq.h>
#include <os/schedule.h>
#include <os/virmem.h>
#include <arch/config.h>
#include <lib/assert.h>
#include <arch/gate.h>

#ifdef ENABLE_SMP
// indiate apic whether has been map to virmem on page enable
static uint8_t apic_has_map = 0;

// global APIC
address_t apic;
address_t LAPIC_ID;
address_t LAPIC_VERSION;
address_t LVT_CMCI;
address_t LVT_TIMER;
address_t LVT_THERMAL;
address_t LVT_PERFORMANCE_COUNT;
address_t LVT_LINT0;
address_t LVT_LINT1;
address_t LVT_ERROR;
address_t ICR;
address_t LOGICAL_DEST;
address_t DEST_FORMAT;
address_t ERROR_STATUS;
address_t EOF_INT;
address_t ISR;
address_t IRR;
address_t TIMER_INIT_COUNT;
address_t TIMER_CUR_COUNT;
address_t DIVIDE_CONFIG;

uint8_t apic_init = 0; // init flags

static void APICWrite(apic_reg_t type, uint32_t val);
static uint32_t APICRead(apic_reg_t type);
static void APICLvtInit();

uint32_t apic_base = 0;

// check the APIC support in system
int CheckAPICSupport()
{
    uint32_t a, b, c, d;

    // check CPUID
    X86CPUID(CPU_GETMODEL, (intptr_t)&a, (intptr_t)&b, (intptr_t)&c, (intptr_t)&d);

    if (d & CPU_FEATURE_LOCAL_APIC)
    {
        return 1;
    }
    return 0;
}

// get APIC base from MSR
address_t GetAPICMSRBase()
{
    uint32_t eax, edx;

    ReadMSR(IA32_APIC_BASE_MSR, &eax, &edx);

    if (eax & IA32_APIC_BASE_MSR_BSP)
    {
        KPrint("[APIC] cpu is BSP\n");
    }
    return (address_t)(eax & IA32_APIC_BASE_MSR_BASE);
}

// set APIC base to MSR and enable hardware
void SetAPICMSRBase()
{
    uint32_t eax = 0;
    uint32_t edx = 0;

    ReadMSR(IA32_APIC_BASE_MSR, &eax, &edx);
    eax |= IA32_APIC_BASE_MSR_ENABLE;
    WriteMSR(IA32_APIC_BASE_MSR, eax, edx);
    KPrint("[apic] set msr ok\n");
}

void APICMapVbase()
{
    // if is page enable
    if (page_enable)
    {
        uint32_t vbase = KMemAlloc(PAGE_SIZE);
        if (!vbase)
            Panic("map apic to vmem err\n");

        HalMemIoReMap(vbase, apic, PAGE_SIZE);
        apic = vbase;
        // get APIC register
        LAPIC_ID = ((address_t)apic + APIC_LAPIC_ID_REG);
        LAPIC_VERSION = ((address_t)apic + APIC_LAPIC_VERSION_REG);
        LVT_CMCI = ((address_t)apic + APIC_LVT_CMCI_REG);
        LVT_TIMER = ((address_t)apic + APIC_LVT_TIMER_REG);
        LVT_THERMAL = ((address_t)apic + APIC_LVT_THERMAL_MONITOR_REG);
        LVT_PERFORMANCE_COUNT = ((address_t)apic + APIC_LVT_PERFORMANCE_MONITOR_REG);
        LVT_LINT0 = ((address_t)apic + APIC_LVT_LINT0_REG);
        LVT_LINT1 = ((address_t)apic + APIC_LVT_LINT1_REG);
        LVT_ERROR = ((address_t)apic + APIC_LVT_ERROR_REG);
        ICR = ((address_t)apic + APIC_ICR_LOW_REG);
        ERROR_STATUS = ((address_t)apic + APIC_ERR_STATUS_REG);
        EOF_INT = ((address_t)apic + APIC_EOF_REG);
        ISR = ((address_t)apic + APIC_ISR_REG);
        IRR = ((address_t)apic + APIC_IRR_REG);
        DIVIDE_CONFIG = ((address_t)apic + APIC_DIVIDE_REG);
        TIMER_INIT_COUNT = ((address_t)apic + APIC_TIMER_INIT_COUNT_REG);
        TIMER_CUR_COUNT = ((address_t)apic + APIC_TIMER_CURRENT_COUNT_REG);
        LOGICAL_DEST = ((address_t)apic + APIC_LOGICAL_DEST_REG);
        // map to high base
        KPrint("[apic] APIC vbase: %x\n", apic);
    }
    DebugAPIC();
}

// enable APIC on system
void EnableAPIC()
{

    apic = GetAPICMSRBase();
    KPrint("[apic] APIC pybase: %x\n", apic);

    // get APIC register
    LAPIC_ID = ((address_t)apic + APIC_LAPIC_ID_REG);
    LAPIC_VERSION = ((address_t)apic + APIC_LAPIC_VERSION_REG);
    LVT_CMCI = ((address_t)apic + APIC_LVT_CMCI_REG);
    LVT_TIMER = ((address_t)apic + APIC_LVT_TIMER_REG);
    LVT_THERMAL = ((address_t)apic + APIC_LVT_THERMAL_MONITOR_REG);
    LVT_PERFORMANCE_COUNT = ((address_t)apic + APIC_LVT_PERFORMANCE_MONITOR_REG);
    LVT_LINT0 = ((address_t)apic + APIC_LVT_LINT0_REG);
    LVT_LINT1 = ((address_t)apic + APIC_LVT_LINT1_REG);
    ICR = ((address_t)apic + APIC_ICR_LOW_REG);
    ERROR_STATUS = ((address_t)apic + APIC_ERR_STATUS_REG);
    EOF_INT = ((address_t)apic + APIC_EOF_REG);
    ISR = ((address_t)apic + APIC_ISR_REG);
    IRR = ((address_t)apic + APIC_IRR_REG);
    DIVIDE_CONFIG = ((address_t)apic + APIC_DIVIDE_REG);
    TIMER_INIT_COUNT = ((address_t)apic + APIC_TIMER_INIT_COUNT_REG);
    TIMER_CUR_COUNT = ((address_t)apic + APIC_TIMER_CURRENT_COUNT_REG);
    LOGICAL_DEST = ((address_t)apic + APIC_LOGICAL_DEST_REG);
    // enable hardware APIC
    SetAPICMSRBase();
    KPrint("[apic] Now xAPIC has been enable!\n");
    // get bsp processor ID
    bsp_id = APICGetID();
    DebugAPIC();
}

void DebugAPIC()
{
    KPrint("CPU ID: %d,Version: %x,Max LVT: %d\n", (*(uint32_t *)LAPIC_ID & APIC_LAPIC_ID_MASK) >> APIC_LAPIC_ID_SHIFT, (*(uint32_t *)LAPIC_VERSION & APIC_LAPIC_VERSION_MASK), (*(uint32_t *)LAPIC_VERSION & APIC_LAPIC_MAX_LVT_ENTRY) >> 16);
    KPrint("Mapaddr: %x,APIC ID: %d\n", apic, ((*(uint32_t *)LOGICAL_DEST) & APIC_LOGICAL_DEST_APICID_MASK) >> APIC_LVT_TRGMODE_SHIFT);
}

// APIC timer handler function
void APICTimerUpdate()
{
    //KPrint("apic timer handler\n");
    //   sys ticks was update with PIT
    processor_info_t *proc = MpGetProcessor();
    // update cpu ticks
    proc->ticks++;
    // active softirq
    SoftirqActive(SCHED_SOFTIRQ);
    APICSendEOF();
}

uint8_t APICGetID()
{
    return (uint8_t)((APICRead(APIC_ID) & APIC_LAPIC_ID_MASK) >> APIC_LAPIC_ID_SHIFT);
}

uint8_t APICGetVer()
{
    return (uint8_t)(APICRead(APIC_VER) & APIC_LAPIC_VERSION_MASK);
}

void APICInit()
{
    // check APIC support
    if (!CheckAPICSupport())
    {
        KPrint("your system no support APIC!\n");
        return;
    }
    // start APIC
    EnableAPIC();
    // init LVT
    APICLvtInit();
    apic_init = 1;
    KPrint("[APIC] Init APIC ok\n");
}

void APICTimerInit(uint32_t ticks)
{
    KPrint("start init apic timer\n");
    uint32_t frequery;
    processor_info_t *proc = MpGetProcessor();
    if (!proc)
        Panic("[apic timer] get processor err\n");

    // set divide by 16
    APICWrite(APIC_DIVIDE_CFG, APIC_DIVIDE_VAL_16);
    // only figure bus frequery on BSP processor when in SMP mode
    if (proc->APIC_ID == bsp_id)
    {
        // reset APIC timer
        APICWrite(APIC_INIT_COUNT, 0xffffffff);
        // wait pit to 0
        while (!In8(0x61) & 0x20)
            ;
        // get timer count
        uint32_t end = APICRead(APIC_CUR_COUNT);
        // get cpu bus freqery
        frequery = (0xffffffff - end) * 16 * HZ;
        // save cpu bus frequery
        proc->bus_frequery = frequery;
        KPrint("[APIC Timer] bus frequery %x\n", proc->bus_frequery);
    }
    // get bus frequery
    frequery = MpGetBspProcessor()->bus_frequery;
    // save timer frequery
    uint32_t cycle = frequery / 16;
    proc->timer_frequery = cycle;

    uint32_t count = ticks < 0 ? 1 : ticks;
    // set APIC timer
    APICWrite(APIC_INIT_COUNT, count < 16 ? 16 : count);
    // set divide by 16 again
    APICWrite(APIC_DIVIDE_CFG, APIC_DIVIDE_VAL_16);
    // start timer
    APICSetLVT(APIC_LVT_TIMER, LOCAL_TIMER_VECTOR, 0, 0, 0, APIC_ENINTERRUPT, APIC_LVT_TIMER_MODE_PERIODIC);
    KPrint("[APIC] init APIC timer ok\n");
}

static void APICLvtInit()
{
    // LVT CMCI
    APICSetLVT(APIC_LVT_CMCI, 0, 0, 0, 0, APIC_DISINTERRUPT, 0);
    // LVT Timer
    APICSetLVT(APIC_LVT_TIMER, LOCAL_TIMER_VECTOR, 0, 0, 0, APIC_DISINTERRUPT, 0);
    // LVT Thermal
    APICSetLVT(APIC_LVT_THERMAL, 0, 0, 0, 0, APIC_DISINTERRUPT, 0);
    // LVT Performance
    APICSetLVT(APIC_LVT_PERFORMANCE, 0, 0, 0, 0, APIC_DISINTERRUPT, 0);
    // LVT LINT0(connected to extern PICs)
    APICSetLVT(APIC_LVT_LINT0, 0, APIC_LVT_DELIVERY_MODE_EXTINT, 0, APIC_LVT_TRGMODE_LEVEL, APIC_ENINTERRUPT, 0);
    // LVT LINT1(NMI)
    APICSetLVT(APIC_LVT_LINT1, 0, APIC_LVT_DELIVERY_MODE_NMI, 0, APIC_LVT_TRGMODE_EDGE, APIC_ENINTERRUPT, 0);
    // LVT error
    APICSetLVT(APIC_LVT_ERR, LOCAL_ERROR_VECTOR, APIC_LVT_DELIVERY_MODE_FIX, 0, APIC_LVT_TRGMODE_EDGE, APIC_ENINTERRUPT, 0);
    KPrint("[APIC] all LVT set ok\n");
}

void APICSendIPI(uint64_t vendor, uint64_t delivery_mode, uint64_t dest, uint64_t dest_mode, uint64_t dest_short, uint64_t trigger, uint64_t level)
{
    KPrint("[apic] send IPI to cpu\n");
    KPrint("vendor %x delivery_mode %x dest %x\ndest_short %x trigger %x level %x\n", (uint32_t)vendor, (uint32_t)delivery_mode, (uint32_t)dest, (uint32_t)dest_short, (uint32_t)trigger, (uint32_t)level);
    // check ICR delivery status and polling
    while ((*(uint64_t *)ICR & APIC_ICR_DELIVERY_STATUS_MASK) != APIC_ICR_DELIVERY_STATUS_IDLE)
    {
        CpuDoNoting();
    }
    uint64_t icr = ((dest_short & 0x3) << APIC_ICR_DEST_SHORHAND_SHIFT) | ((trigger & 0x3) << APIC_ICR_TRIGMODE_SHIFT) | ((level & 0x3) << APIC_ICR_LEVEL_SHIFT) | ((dest_mode & 0x3) << APIC_ICR_DEST_MODE_SHIFT) | ((delivery_mode & 0x7) << APIC_ICR_DELIVERY_MODE_SHIFT);

    // send specific vendor to CPUs
    if ((delivery_mode == APIC_ICR_DELIVERY_MODE_FIX) || (delivery_mode == APIC_ICR_DELIVERY_MODE_LOW_PRIOR) || (delivery_mode == APIC_ICR_DELIVERY_MODE_STARTUP))
    {
        if (vendor < 0 && vendor >= 256)
        {
            KPrint("Vendor invalid!\n");
            return;
        }
        icr |= (vendor & 0xff);
    }

    // NMI vendor default is 2
    if (delivery_mode == APIC_ICR_DELIVERY_MODE_NMI)
    {
        icr |= 2;
    }

    // send IPI to specific CPUs
    if (dest_short == APIC_ICR_DEST_SHORTHAND_NONE)
    {
        icr |= (dest & 0xFF) << APIC_ICR_DEST_SHIFT;
    }

    KPrint("icr low %x high %x\n", (uint32_t)icr, (uint32_t)(icr >> 32));

    APICWrite(APIC_ICR_HIGH, (uint32_t)(icr >> 32));
    APICWrite(APIC_ICR_LOW, (uint32_t)icr);
}

void APICPrintError()
{
    // write 0 to register when ready to read
    APICWrite(APIC_ERR, 0);

    uint32_t error = APICRead(APIC_ERR);
    KPrint("\n----------APIC ERROR REGISTER----------\n");
    if (error & APIC_ERR_STATUS_SEND_CHKSUM)
    {
        KPrint("[APIC] Send Checksum Error!\n");
    }

    if (error & APIC_ERR_STATUS_SEND_ACCEPT)
    {
        KPrint("[APIC] Send Accept Error!\n");
    }

    if (error & APIC_ERR_STATUS_SEND_ILLEGAL_VEC)
    {
        KPrint("[APIC] Send Illegal Vector on 0-15!\n");
    }

    if (error & APIC_ERR_STATUS_RECV_CHKSUM)
    {
        KPrint("[APIC] Receive Checksum Error!\n");
    }

    if (error & APIC_ERR_STATUS_RECV_ACEEPT)
    {
        KPrint("[APIC] Receive Accept Error!\n");
    }

    if (error & APIC_ERR_STATUS_RECV_ILLEGAL_VEC)
    {
        KPrint("[APIC] Receive Illegal Vector on 0-15!\n");
    }

    if (error & APIC_ERR_STATUS_REDIRCT_IPI)
    {
        KPrint("[APIC] CPU no support Lowest Priority Mode IPI!\n");
    }

    if (error & APIC_ERR_STATUS_ILLEGAL_REGADDR)
    {
        KPrint("[APIC] Try to Access no support register!\n");
    }
}

// set LVT table entry
void APICSetLVT(apic_lvt_t lvt_type, uint32_t vector, uint32_t delivey_mode, uint32_t pin, uint32_t trig, uint32_t mask, uint32_t timer_mode)
{
    uint32_t lvt = 0;
    address_t lvt_entry[] = {LVT_CMCI, LVT_TIMER, LVT_THERMAL, LVT_PERFORMANCE_COUNT, LVT_LINT0, LVT_LINT1, LVT_ERROR};

    if (mask)
    {
        lvt |= 1 << APIC_LVT_MASK_SHIFT;
    }

    // send vector to cpu
    if (vector < 0 || vector > 255)
    {
        KPrint("[APIC] LVT vector invalid!\n");
        return;
    }
    lvt |= vector;

    if (lvt_type != APIC_LVT_TIMER)
        lvt |= (delivey_mode & 0x7) << APIC_LVT_DELIVERY_MODE_SHIFT;

    // set trigger mode and interrupt pin
    if (lvt_type == APIC_LVT_LINT0 || lvt_type == APIC_LVT_LINT1)
    {
        lvt |= (trig & 0x1) << APIC_LVT_TRGMODE_SHIFT | (pin & 0x1) << APIC_LVT_PIN_SHIFT;
    }
    else
    {
        // set timer mode
        if (lvt_type == APIC_LVT_TIMER)
        {
            lvt |= (timer_mode & 0x3) << APIC_LVT_TIMER_MODE_SHIFT;
        }
    }

    // write register
    *(uint32_t *)lvt_entry[lvt_type] = lvt;
}

// end of interrupt
void APICSendEOF()
{
    APICWrite(APIC_EOF, 0);
}

// check whether the vector is on interrupt server
int APICOnISR(uint8_t vec)
{
    address_t isr = ISR + (vec / 32) * 4;

    return *(uint32_t *)isr & (1 << (vec % 32));
}

// check whether the vector is on interrupt request
int APICOnIRR(uint8_t vec)
{
    address_t irr = IRR + (vec / 32) * 4;

    return *(uint32_t *)irr & (1 << (vec % 32));
}

// set model
void APICSetModel(uint32_t model)
{
    APICWrite(APIC_DESTMODE, (model & 0xF) << APIC_DEST_FORMAT_MODEL_SHIFT);
}

static void APICWrite(apic_reg_t type, uint32_t val)
{
    address_t base[] = {LAPIC_ID, LAPIC_VERSION, DEST_FORMAT, LOGICAL_DEST, ICR, ICR + 0x10, EOF_INT, ERROR_STATUS, TIMER_INIT_COUNT, TIMER_CUR_COUNT, DIVIDE_CONFIG};

    // write register
    *(uint32_t *)base[type] = val;
}

static uint32_t APICRead(apic_reg_t type)
{
    address_t base[] = {LAPIC_ID, LAPIC_VERSION, DEST_FORMAT, LOGICAL_DEST, ICR, ICR + 0x10, EOF_INT, ERROR_STATUS, TIMER_INIT_COUNT, TIMER_CUR_COUNT, DIVIDE_CONFIG};

    // read register
    return *(uint32_t *)base[type];
}

#endif